﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace ParticleEffects
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    /// 

    public class Athruster
    {

        #region thruster variables
        public Model model;
        public Texture3D Noise;

        public EffectTechnique technique;
        public EffectParameter shader_matrices;
        public EffectParameter thrust_color;
        public EffectParameter effect_tick;
        public EffectParameter effect_noise;

        public Color[] colors = new Color[2];
        public Vector4[] v4colors = new Vector4[2];

        public float heat = 1; // controls the length of the thrust
        public float tick = 0; // controls the rate of thrust

        public Matrix world_matrix;
        public Matrix inverse_scale_transpose;
        public Matrix scale;
        public Matrix[] matrices_combined = new Matrix[3];

        public Vector3 Up;
        public Vector3 Right;
        public Vector3 dir_to_camera;

        public float allscale = 1;
        #endregion

        public Athruster() { }

        private float _thrustspeed = 0;
        private float _thrustsize = 0;
        private Color _color_at_exhaust;
        private Color _color_at_end;
        private Vector3 _scale_factors;
        public Vector3 _position;

        public Athruster(Vector3 position, float scale, float speed, float size, Color c_at_exhaust, Color c_at_end) 
        {
            _scale_factors = Vector3.One * scale;
            _thrustspeed = speed;
            _thrustsize = size;
            _color_at_exhaust = c_at_exhaust;
            _color_at_end = c_at_end;
            _position = position;
        }

        public void load_and_assign_effects(ContentManager content, string filename, string noisefilename, Effect effect)
        {
            model = content.Load<Model>(filename);
            Noise = content.Load<Texture3D>(noisefilename);

            model.Meshes[0].MeshParts[0].Effect = effect;

            technique = effect.Techniques["thrust_technique"];
            shader_matrices = effect.Parameters["world_matrices"];
            thrust_color = effect.Parameters["thrust_color"];
            effect_tick = effect.Parameters["ticks"];
            effect_noise = effect.Parameters["noise_texture"];

            world_matrix = Matrix.Identity;
        }

        public void update(Vector3 Put_me_at,
                           Vector3 Point_me_at,
                           Vector3 Scale_factors,
                           float thrustsize,
                           float thrustspeed,
                           Color color_at_exhaust,
                           Color color_at_end,
                           Vector3 camera_position)
        {
            _thrustspeed = thrustspeed;
            _thrustsize = thrustsize;
            _color_at_end = color_at_end;
            _color_at_exhaust = color_at_exhaust;
            _scale_factors = Scale_factors;

            update(Put_me_at, Point_me_at, camera_position);
        }

        public void update(Vector3 Put_me_at,
                           Vector3 Point_me_at,
                           Vector3 camera_position)
        {

            heat = MathHelper.Clamp(_thrustsize, 0, 1);

            // rate of thrust
            tick += _thrustspeed;

            // color[0] is the first color coming out of the thruster, 1 is the second.
            colors[0] = _color_at_end;
            colors[1] = _color_at_exhaust;
            //====================================================


            // these manipulations are necessary to get its edges to fade correctly
            // it keeps the "right" vector on a plane that goes through the camera

            world_matrix = Matrix.Identity;

            world_matrix.Forward = Point_me_at;

            #region calculate direction to camera
            dir_to_camera.X = Put_me_at.X - camera_position.X;
            dir_to_camera.Y = Put_me_at.Y - camera_position.Y;
            dir_to_camera.Z = Put_me_at.Z - camera_position.Z;

            #endregion

            Vector3.Cross(ref Point_me_at, ref dir_to_camera, out Up); // calculate UP

            Up.Normalize();

            Vector3.Cross(ref Point_me_at, ref Up, out Right); // Calculate Right

            world_matrix.Right = Right;
            world_matrix.Up = Up;

            Matrix.CreateScale(ref _scale_factors, out scale);  // Create a scale matrix,if you have scale

            Matrix.Multiply(ref world_matrix, ref scale, out world_matrix); // scale the world matrix

            inverse_scale_transpose = Matrix.Transpose(Matrix.Invert(world_matrix));
            // the inverst transpose above is to re-adjust the normals in the shader

            world_matrix.Translation = Put_me_at;

            //====================================================

        }

        #region use these two functions if you are using the code to draw many many many thrusters (see game draw)

        public void set_technique(Effect effect)
        {
            effect.CurrentTechnique = technique;
        }

        public void prepare_effect(Matrix view, Matrix project, Effect effect)
        {
            matrices_combined[0] = world_matrix;
            matrices_combined[1] = world_matrix * view * project;
            matrices_combined[2] = inverse_scale_transpose;

            shader_matrices.SetValue(matrices_combined);

            v4colors[0] = colors[0].ToVector4();
            v4colors[1] = colors[1].ToVector4();
            v4colors[0].W = heat;

            thrust_color.SetValue(v4colors);
            effect_tick.SetValue(tick);
            effect_noise.SetValue(Noise);

            effect.CommitChanges();

        }

        #endregion

        #region use this if you just have a few thrusters
        public void draw(ref Matrix view, ref Matrix project, Effect effect)
        {
            matrices_combined[0] = world_matrix;
            matrices_combined[1] = world_matrix * view * project;
            matrices_combined[2] = inverse_scale_transpose;

            effect.CurrentTechnique = technique;
            shader_matrices.SetValue(matrices_combined);

            v4colors[0] = colors[0].ToVector4();
            v4colors[1] = colors[1].ToVector4();
            v4colors[0].W = heat;

            thrust_color.SetValue(v4colors);
            effect_tick.SetValue(tick);
            effect_noise.SetValue(Noise);

            model.Meshes[0].Draw();
        }
        #endregion
    }

    public class ViperThruster
    {

        Game _game;
        ArrayList<Athruster> _thrusters;

        public ArrayList<Athruster> Thrusters
        {
            get { return _thrusters; }
            set { _thrusters = value; }
        }

        public Vector3 Vector {
            get { return Thrusters[0]._position; }
        }

        Effect _effect;

        public ViperThruster(Game game)
        {
            _game = game;
            _thrusters = new ArrayList<Athruster>(50);
            _effect = _game.Content.Load<Effect>(@"Effects/Thruster/Thrust");
            _renderState = new MyRenderState();
            _thrusters.Add(new Athruster(-Vector3.UnitZ * 4.0f + 1.7f * Vector3.UnitY, 1.2f, 0.05f, 2.5f, Color.Blue, Color.CornflowerBlue));
            _thrusters[_thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", _effect);
            _thrusters.Add(new Athruster(-Vector3.UnitZ * 4.0f + 1.2f * Vector3.UnitY + 0.7f * Vector3.UnitX, 1.2f, 0.05f, 2.5f, Color.Blue, Color.CornflowerBlue));
            _thrusters[_thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", _effect);
            _thrusters.Add(new Athruster(-Vector3.UnitZ * 4.0f + 1.2f * Vector3.UnitY - 0.7f * Vector3.UnitX, 1.2f, 0.05f, 2.5f, Color.Blue, Color.CornflowerBlue));
            _thrusters[_thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", _effect);
        }

        public void Update(Vector3 camPosition, Matrix shipWorldM)
        {
            //_thrusters.For_Each(t => t.update(Vector3.Transform(t._position, shipWorldM),
            //                                 Vector3.UnitX,
            //                                 camPosition));
            for (int i = 0; i < _thrusters.Count; i++)
            {
                var t = _thrusters[i];
                t.update(Vector3.Transform(t._position, shipWorldM),
                                             Vector3.UnitX,
                                             camPosition);
            }
        }

        MyRenderState _renderState;
        public void Draw(GameTime gameTime, Matrix view, Matrix projection)
        {
            GraphicsDevice device = _game.GraphicsDevice;

            _renderState.AlphaBlendEnable = device.RenderState.AlphaBlendEnable;
            _renderState.SourceBlend = device.RenderState.SourceBlend;
            _renderState.DestinationBlend = device.RenderState.DestinationBlend;

            _renderState.DepthBufferWriteEnable = device.RenderState.DepthBufferWriteEnable;
            _renderState.CullMode = device.RenderState.CullMode;

            device.RenderState.AlphaBlendEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.One;

            device.RenderState.DepthBufferWriteEnable = false;
            device.RenderState.CullMode = CullMode.None;

            //_thrusters.For_Each(t => t.set_technique(_effect));
            for (int i = 0; i < _thrusters.Count; i++)
            {
                _thrusters[i].set_technique(_effect);
            }

            _effect.Begin(SaveStateMode.SaveState);
            _effect.CurrentTechnique.Passes[0].Begin();

            //_thrusters.For_Each(t =>
            //{
            //    setup_index_and_vertices(t.model.Meshes[0], device);
            //    t.prepare_effect(view, projection, _effect);
            //    draw_mesh(t.model.Meshes[0], device);
            //});

            for (int i = 0; i < _thrusters.Count; i++)
            {
                var t = _thrusters[i];
                setup_index_and_vertices(t.model.Meshes[0], device);
                t.prepare_effect(view, projection, _effect);
                draw_mesh(t.model.Meshes[0], device);
            }

            _effect.CurrentTechnique.Passes[0].End();
            _effect.End();

            device.RenderState.AlphaBlendEnable = _renderState.AlphaBlendEnable;
            device.RenderState.SourceBlend = _renderState.SourceBlend;
            device.RenderState.DestinationBlend = _renderState.DestinationBlend;

            device.RenderState.DepthBufferWriteEnable = _renderState.DepthBufferWriteEnable;
            device.RenderState.CullMode = _renderState.CullMode;
        }

        public static void setup_index_and_vertices(ModelMesh mesh, GraphicsDevice device)
        {
            device.VertexDeclaration = mesh.MeshParts[0].VertexDeclaration;
            device.Vertices[0].SetSource(mesh.VertexBuffer,
                                                                    mesh.MeshParts[0].StreamOffset,
                                                                    mesh.MeshParts[0].VertexStride);
            device.Indices = mesh.IndexBuffer;
        }

        public static void draw_mesh(ModelMesh mesh, GraphicsDevice device)
        {
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, mesh.MeshParts[0].BaseVertex, 0,
                                                                     mesh.MeshParts[0].NumVertices, mesh.MeshParts[0].StartIndex,
                                                                     mesh.MeshParts[0].PrimitiveCount);
        }
    }

    public class CylonThruster
    {

        Game _game;
        ArrayList<Athruster> _thrusters;
        Effect _effect;

        public CylonThruster(Game game)
        {
            _game = game;
            _thrusters = new ArrayList<Athruster>(50);
            _effect = _game.Content.Load<Effect>(@"Effects/Thruster/Thrust");
            _renderState = new MyRenderState();

            _thrusters.Add(new Athruster(-Vector3.UnitZ * 4.0f + 0.1f * Vector3.UnitY, 0.8f, 0.05f, 2.5f, Color.OrangeRed, Color.Red));
            _thrusters[_thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", _effect);
            _thrusters.Add(new Athruster(-Vector3.UnitZ * 4.0f + -0.05f * Vector3.UnitY + 0.7f * Vector3.UnitX, 0.5f, 0.05f, 2.5f, Color.OrangeRed, Color.Red));
            _thrusters[_thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", _effect);
            _thrusters.Add(new Athruster(-Vector3.UnitZ * 4.0f + -0.02f * Vector3.UnitY - 0.7f * Vector3.UnitX, 0.5f, 0.05f, 2.5f, Color.OrangeRed, Color.Red));
            _thrusters[_thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", _effect);
        }

        public void Update(Vector3 camPosition, Matrix shipWorldM)
        {
            //_thrusters.For_Each(t => t.update(Vector3.Transform(t._position, shipWorldM),
            //                                 -Vector3.UnitX,
            //                                 camPosition));
            for (int i = 0; i < _thrusters.Count; i++)
            {
                var t = _thrusters[i];
                t.update(Vector3.Transform(t._position, shipWorldM),
                                             -Vector3.UnitX,
                                             camPosition);
            }
        }

        MyRenderState _renderState;
        public void Draw(GameTime gameTime, Matrix view, Matrix projection, Matrix shipWorldM, Vector3 camPosition)
        {
            Update(camPosition, shipWorldM);

            GraphicsDevice device = _game.GraphicsDevice;

            _renderState.AlphaBlendEnable = device.RenderState.AlphaBlendEnable;
            _renderState.SourceBlend = device.RenderState.SourceBlend;
            _renderState.DestinationBlend = device.RenderState.DestinationBlend;

            _renderState.DepthBufferWriteEnable = device.RenderState.DepthBufferWriteEnable;
            _renderState.CullMode = device.RenderState.CullMode;

            device.RenderState.AlphaBlendEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.One;

            device.RenderState.DepthBufferWriteEnable = false;
            device.RenderState.CullMode = CullMode.None;

            //_thrusters.For_Each(t => t.set_technique(_effect));
             for (int i = 0; i < _thrusters.Count; i++)
                _thrusters[i].set_technique(_effect);

            _effect.Begin(SaveStateMode.SaveState);
            _effect.CurrentTechnique.Passes[0].Begin();

            //_thrusters.For_Each(t =>
            //{
            //    setup_index_and_vertices(t.model.Meshes[0], device);
            //    t.prepare_effect(view, projection, _effect);
            //    draw_mesh(t.model.Meshes[0], device);
            //});
            for (int i = 0; i < _thrusters.Count; i++)
            {
                var t = _thrusters[i];
                setup_index_and_vertices(t.model.Meshes[0], device);
                t.prepare_effect(view, projection, _effect);
                draw_mesh(t.model.Meshes[0], device);
            }

            _effect.CurrentTechnique.Passes[0].End();
            _effect.End();

            device.RenderState.AlphaBlendEnable = _renderState.AlphaBlendEnable;
            device.RenderState.SourceBlend = _renderState.SourceBlend;
            device.RenderState.DestinationBlend = _renderState.DestinationBlend;

            device.RenderState.DepthBufferWriteEnable = _renderState.DepthBufferWriteEnable;
            device.RenderState.CullMode = _renderState.CullMode;
            //_thrusters.For_Each(t => t.draw(view, projection, _effect));
            for (int i = 0; i < _thrusters.Count; i++)
                _thrusters.draw(view, projection, _effect);
        }

        public static void setup_index_and_vertices(ModelMesh mesh, GraphicsDevice device)
        {
            device.VertexDeclaration = mesh.MeshParts[0].VertexDeclaration;
            device.Vertices[0].SetSource(mesh.VertexBuffer,
                                                                    mesh.MeshParts[0].StreamOffset,
                                                                    mesh.MeshParts[0].VertexStride);
            device.Indices = mesh.IndexBuffer;
        }

        public static void draw_mesh(ModelMesh mesh, GraphicsDevice device)
        {
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, mesh.MeshParts[0].BaseVertex, 0,
                                                                     mesh.MeshParts[0].NumVertices, mesh.MeshParts[0].StartIndex,
                                                                     mesh.MeshParts[0].PrimitiveCount);
        }
    }

    //public class ThrusterManager
    //{ 
    //    private Game _game;
    //    private ArrayList<Athruster> thrusters;
    //    Effect effect;

    //    public ThrusterManager(Game game)
    //    {
    //        _game = game;
    //        Initialize();
    //    }

    //    public void Initialize()
    //    {
    //        effect = _game.Content.Load<Effect>("Thruster/Thrust");
    //        thrusters = new ArrayList<Athruster>(50);
    //    }

    //    public void addThruster(Vector3 absolutePosition, float speed, float scale, float size, Color c_at_exhaust, Color c_at_end)
    //    {
    //        thrusters.Add(new Athruster(ship, absolutePosition, scale, speed, size, c_at_exhaust, c_at_end));
    //        thrusters[thrusters.Count - 1].load_and_assign_effects(_game.Content, "Thruster/ThrustCylinderB", "Thruster/NoiseVolume", effect);
    //    }

    //    public void Update()
    //    {
    //        thrusters.For_Each(t => t.update(Vector3.Transform(t._position, t.Ship.World),
    //                                        Matrix.CreateFromQuaternion(t.Ship.Orientation).Forward,
    //                                        _game.campos));
    //        for(int i=0;i<thrusters.Count;i++)
    //            thrusters.update(Vector3.Transform(t._position, t.Ship.World),
    //                             Matrix.CreateFromQuaternion(t.Ship.Orientation).Forward,
    //                             _game.campos)
    //    }

    //    public void Draw()
    //    {
    //        drawThrust();
    //    }

    //    private void drawThrust()
    //    {
    //        GraphicsDeviceManager graphics = _game.gdm;

    //        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
    //        graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
    //        graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.One;

    //        graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
    //        graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;

    //        thrusters.For_Each(t => t.set_technique(effect));
    //        for(int i = 0;i < thrusters.Count; i++)
    //            thrusters[i].set_technique(effect);

    //        effect.Begin(SaveStateMode.SaveState);
    //        effect.CurrentTechnique.Passes[0].Begin();

    //        thrusters.For_Each(t => {
    //            setup_index_and_vertices(t.model.Meshes[0], graphics.GraphicsDevice);
    //            t.prepare_effect(ref _game.viewMatrix, ref _game.projectionMatrix, effect);
    //            draw_mesh(t.model.Meshes[0], graphics.GraphicsDevice);
    //        });

    //        for(int i= 0;i<thrusters.Count;i++)
    //        {
    //            var t = thrusters[i];
    //            setup_index_and_vertices(t.model.Meshes[0], graphics.GraphicsDevice);
    //            t.prepare_effect(ref _game.viewMatrix, ref _game.projectionMatrix, effect);
    //            draw_mesh(t.model.Meshes[0], graphics.GraphicsDevice);
    //        }
    //        effect.CurrentTechnique.Passes[0].End();
    //        effect.End();

    //        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
    //        graphics.GraphicsDevice.RenderState.SourceBlend = Blend.One;
    //        graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.Zero;

    //        graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
    //        graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
    //    }

    //    public static void setup_index_and_vertices(ModelMesh mesh, GraphicsDevice device)
    //    {
    //        device.VertexDeclaration = mesh.MeshParts[0].VertexDeclaration;
    //        device.Vertices[0].SetSource(mesh.VertexBuffer,
    //                                                                mesh.MeshParts[0].StreamOffset,
    //                                                                mesh.MeshParts[0].VertexStride);
    //        device.Indices = mesh.IndexBuffer;
    //    }

    //    public static void draw_mesh(ModelMesh mesh, GraphicsDevice device)
    //    {
    //        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, mesh.MeshParts[0].BaseVertex, 0,
    //                                                                 mesh.MeshParts[0].NumVertices, mesh.MeshParts[0].StartIndex,
    //                                                                 mesh.MeshParts[0].PrimitiveCount);
    //    }
    //}
}
